SRP is vulnerable to pre-computation attacks, due to the fact that it hands over the user’s “salt” to any attacker who can start an SRP session. This means I can ask a server for your salt, and build a dictionary of potential password hashes even before the server is compromised.
fromcryptopals_libimport*importos,hashlibdefgenerate_key_pair(prime,g):secret_exp=secure_rand_between(2,prime-2)transmit_key=pow(g,secret_exp,prime)return[{"g":g,"p":prime,"key":transmit_key},{"g":g,"p":prime,"exp":secret_exp}]classSRPServer():def__init__(self,k,prime,g,hash_obj=hashlib.sha256):self.k=kself.prime=prime# Added: store prime for validation
self.g=g# Added: store g
self.hash_obj=hash_objself.database={}self.auth_database={}defregister_user(self,username,salt,verifier):ifusernameinself.database:raiseValueError(f"Error: User {username} already in database")else:self.database[username]={"salt":salt,"verifier":verifier}print(f"[Server] Registered user: {username}")defauthenticate_init(self,username,client_pub_key):ifusernamenotinself.database:raiseValueError(f"Error: {username} is not registered")# SECURITY CHECK: Validate client public key (NEW)
ifclient_pub_key%self.prime==0:raiseValueError("Invalid client public key (A % N == 0)")user=self.database[username]salt, verifier=user["salt"], user["verifier"]session_pub, session_priv=generate_key_pair(self.prime,self.g)server_key=(((self.k*verifier)%self.prime)+session_pub["key"])%self.primeself.auth_database[username]={"client_pub":client_pub_key,"server_key":server_key,"session_priv":session_priv}returnsalt, server_keydefauthenticate_verify(self,username,client_proof):ifusernamenotinself.database:raiseValueError(f"Error: {username} is not registered")user=self.database[username]salt, verifier=user["salt"], user["verifier"]auth=self.auth_database[username]client_pub=auth["client_pub"]server_key=auth["server_key"]session_priv=auth["session_priv"]# Calculate scrambling parameter
u_bytes=self.hash_obj(int_to_bytes(client_pub)+b":"+int_to_bytes(server_key)).digest()# Compute session key
tmp=pow(verifier,bytes_to_int(u_bytes),self.prime)session_s=pow(client_pub*tmp,session_priv["exp"],self.prime)session_k=self.hash_obj(int_to_bytes(session_s)).digest()print(f"[Server] session_key_k: {session_k.hex()}")# Verify client proof
h_n_xor_h_g=fixedlen_xor(self.hash_obj(int_to_bytes(self.prime)).digest(),self.hash_obj(int_to_bytes(self.g)).digest())proof_verify=self.hash_obj(h_n_xor_h_g+b":"+self.hash_obj(username).digest()+b":"+salt+b":"+int_to_bytes(client_pub)+b":"+int_to_bytes(server_key)+b":"+session_k).digest()print(f"[Server] client_proof: {client_proof.hex()}")print(f"[Server] client_proof_verify: {proof_verify.hex()}")ifclient_proof==proof_verify:server_proof=self.hash_obj(int_to_bytes(client_pub)+b":"+client_proof+b":"+session_k).digest()returnserver_proofelse:# CHANGED: Raise exception instead of just printing
raiseValueError("Error: Client verification is invalid")defregister_user(username,password,hash_obj,prime,g,k):server=SRPServer(k,prime,g,hash_obj)# Client Register User
x=hash_obj(salt+b":"+username+b":"+password).digest()verifier=pow(g,bytes_to_int(x),prime)print(f"[Client] Registering User: {username}, verifier: {hex(verifier)} with salt: {salt.hex()}")server.register_user(username,salt,verifier)returnserverdefauthenticate_user(username,password,server,hash_obj,prime,g,k):# Authenticate User
client_pub, client_priv=generate_key_pair(prime,g)print(f"\n[Client] Authenticating User: {username} with public key: {hex(client_pub['key'])}")# Get server salt and server key
salt_from_server, server_public_key=server.authenticate_init(username,client_pub["key"])print(f"[Server] Server sending salt: {salt_from_server.hex()} and public key: {hex(server_public_key)}")# SECURITY CHECK: Validate server public key (NEW)
ifserver_public_key%prime==0:raiseValueError("Invalid server public key (B % N == 0)")# Calculate scrambling parameter
u_bytes=hash_obj(int_to_bytes(client_pub["key"])+b":"+int_to_bytes(server_public_key)).digest()# Compute session key
x=bytes_to_int(hash_obj(salt_from_server+b":"+username+b":"+password).digest())session_s=pow(server_public_key-k*pow(g,x,prime),client_priv["exp"]+bytes_to_int(u_bytes)*x,prime)session_k=hash_obj(int_to_bytes(session_s)).digest()print(f"[Client] session_key_k: {session_k.hex()}")# Generate client proof
h_n_xor_h_g=fixedlen_xor(hash_obj(int_to_bytes(prime)).digest(),hash_obj(int_to_bytes(g)).digest())client_proof=hash_obj(h_n_xor_h_g+b":"+hash_obj(username).digest()+b":"+salt_from_server+b":"+int_to_bytes(client_pub["key"])+b":"+int_to_bytes(server_public_key)+b":"+session_k).digest()# Verify server
server_proof=server.authenticate_verify(username,client_proof)server_proof_verify=hash_obj(int_to_bytes(client_pub["key"])+b":"+client_proof+b":"+session_k).digest()print(f"[Client] server_proof: {server_proof.hex()}")print(f"[Client] server_proof_verify: {server_proof_verify.hex()}")ifserver_proof==server_proof_verify:print("\n[+] AUTHENTICATION SUCCESSFUL!")print(f"[+] Session key established: {session_k.hex()}")else:print("\n[!] SERVER VERIFICATION FAILED!")if__name__=='__main__':# Globals
username=b"admin"password=b"Password123"salt=os.urandom(16)hash_obj=hashlib.sha256# Correct Paramaters from https://datatracker.ietf.org/doc/html/rfc5054#appendix-Aprime=int("AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D",16)g=2k=bytes_to_int(hash_obj(int_to_bytes(prime)+int_to_bytes(g)).digest())%primeserver=register_user(username,password,hash_obj,prime,g,k)authenticate_user(username,password,server,hash_obj,prime,g,k)try:print(f"\n[Client] Attempting login with wrong password...")authenticate_user(username,b"WrongPassword",server,hash_obj,prime,g,k)exceptValueErrorase:print(f"[+] Authentication correctly rejected: {e}")